import Core.Gadget as Gadget
import Performance.FactorModel
import datetime
import numpy as np

# ---批量归因---
def MutualFund_Analysis_1Yr(database, datetime2):
    # ---BM现有数据截止日期---
    datetime1 = datetime2 - datetime.timedelta(days=365)
    documentsTradingDays = Gadget.GetTradingDays(database, datetime1, datetime2, backforword1Day=True)
    lastBMTradingDay = documentsTradingDays[-1]["Date"]
    dataCount = len(documentsTradingDays)

    if lastBMTradingDay < datetime2.date():
        print("Missing Benchmark / Stock DailyBar, Max in Database", lastBMTradingDay, "Target", datetime2.date())
        return
    #
    datetime1 = documentsTradingDays[0]["DateTime"]
    datetime2 = documentsTradingDays[-1]["DateTime"]

    # --- FF3 data ---
    dfFF3Model = Performance.FactorModel.BuildModel_FamaFrench3(database, datetime1, datetime2, save=True)

    # ---公募基金列表---
    filter = {}
    filter["$or"] = [{"InvestType1": "混合型基金"}, {"InvestType1": "股票型基金"}]
    documents = database.Find("Instruments", "MutualFund", filter)

    # ---Loop 公募基金---
    i = 0
    for docMutual in documents:
        i += 1
        symbol = docMutual["Symbol"]
        #
        # if symbol != "000060.OF":
        #     continue
        #
        if docMutual["DateTime2"] < datetime2:
            print(symbol, i, "该基金已退市 @", docMutual["DateTime2"].date())
            continue

        filter = {"Symbol": symbol, "Date": datetime2.date()}
        histAttribute = database.Find("MutualFund", "Performance_Attribution", filter)
        if len(histAttribute) > 0:
            print(symbol, i, "Attribution Data Already Existed @", datetime2.date())
            continue

        #
        filter = {"Symbol": symbol}
        # filter["Date"] = {">=": datetime1.date(), "<=": datetime2.date()}
        documentBars = database.Find("DailyBar", "MutualFund", filter)
        if len(documentBars) == 0:
            print(symbol, i, "该基金无数据")
            continue

        firstTradingDay = documentBars[0]["Date"]
        lastTradingDay = documentBars[-1]["Date"]
        #
        if lastTradingDay < datetime2.date():
            print(symbol, i, "Missing MutualFund DailyBar, Max in Database", lastTradingDay, "Target", datetime2.date())
            continue
        #
        if firstTradingDay > datetime1.date():
            print(symbol, i, "Missing MutualFund DailyBar, Min in Database", firstTradingDay, "Target", datetime1.date())
            continue
        #
        df_Portfolio = MutualFund_PortfolioReturns(database, symbol, datetime1, datetime2)
        validCount = len(df_Portfolio)
        if validCount / dataCount < 0.95:
            print(symbol, i, "Too Much Invalid Data")
            continue

        # --- Portfolio Return Regress on FactorModel ---
        print(symbol, i, "Process Performance Attribution", datetime2, datetime.datetime.now())
        model = Performance.FactorModel.RegressToFactorModel(df_Portfolio[["Date", "Portfolio"]], dfFF3Model, print=False)
        residMean = np.mean(model.resid)
        residStd = np.std(model.resid)

        # ---Save To Database---
        documentPerf = {}
        documentPerf["Symbol"] = symbol
        documentPerf["DateTime1"] = datetime1
        documentPerf["DateTime2"] = datetime2
        documentPerf["DateTime"] = datetime2
        documentPerf["Date"] = datetime2.date()
        documentPerf["AttributeTerm"] = "1Yr"
        documentPerf["Model"] = "FF3"
        documentPerf["Alpha"] = model.params["const"]
        documentPerf["Beta_Market"] = model.params["Market"]
        documentPerf["Beta_Cap"] = model.params["Cap"]
        documentPerf["Beta_PB_LF"] = model.params["PB_LF"]
        documentPerf["Residual_Mean"] = residMean
        documentPerf["Residual_Std"] = residStd
        documentPerf["Key2"] = symbol + "_" + documentPerf["Model"] + "_" + documentPerf["AttributeTerm"] + "_" + Gadget.ToDateString(datetime2.date())
        #
        database.Upsert("MutualFund", "Performance_Attribution", {}, documentPerf)
        b = 0
    a = 0


def MutualFund_PortfolioReturns(database, symbol, datetime1, datetime2):
    filter = {}
    filter["Symbol"] = symbol
    filter["Date"] = {">=": datetime1.date(), "<=": datetime2.date()}
    documents_NAV = database.Find("DailyBar", "MutualFund", filter)
    df_NAV = Gadget.DocumentsToDataFrame(documents_NAV, keep=["Date", "NetAssetValue_Adjusted"])
    df_NAV["Portfolio"] = df_NAV["NetAssetValue_Adjusted"] / df_NAV["NetAssetValue_Adjusted"].shift(1) - 1
    df_NAV.dropna(inplace=True)
    return df_NAV


def MutualFund_Classify(database, symbol, datetime1, datetime2):
    #
    # Regress on Index

    # Regress on Style

    # Misc

    pass


# ---归因指定基金---
def MutualFund_Analysis_FF3(database, symbol, datetime1, datetime2):
    #
    dfTradingDays = Gadget.GetTradingDays_DataFrame(database, datetime1, datetime2, backforword1Day=True)
    realDateTime0 = dfTradingDays.iloc[0]["Date"]
    realDateTime1 = dfTradingDays.iloc[1]["Date"]
    realDateTime2 = dfTradingDays.iloc[-1]["Date"]

    # --- FF3 data ---
    dfFF3Model = Performance.FactorModel.BuildModel_FamaFrench3(database, datetime1, datetime2, save=True)
    # print(dfFF3Model)

    # --- Load Mutual Fund---
    df_Portfolio = MutualFund_PortfolioReturns(database, symbol, realDateTime0, realDateTime2)
    # print(df_Portfolio)

    # --- Portfolio Return Regress on FactorModel ---
    Performance.FactorModel.RegressToFactorModel(df_Portfolio[["Date", "Portfolio"]], dfFF3Model)


if __name__ == '__main__':
    #
    from Core.Config import *
    config = Config()
    database = config.DataBase("MySQL")
    realtime = config.RealTime()

    # ---Test---
    # symbol = "000001.OF"
    # datetime1 = datetime.datetime(2018,1,1)
    # datetime2 = datetime.datetime(2018,2,1)
    # MutualFund_Analysis_FF3(database, symbol, datetime1, datetime2)

    # ---批量归因---
    datetime2 = datetime.datetime(2019, 12, 19)
    MutualFund_Analysis_1Yr(database, datetime2)